package com.kiss.fw.dao;

import java.io.Reader;

import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;

import com.kiss.fw.dto.AbstractDAODTO;
import com.kiss.fw.exceptions.DAOException;
import com.kiss.fw.exceptions.SQLSessionException;
import com.kiss.fw.exceptions.StoredProcedureException;
import com.kiss.fw.utils.Constants;
import com.kiss.fw.utils.Util;

public abstract class AbstractDAO {
	private static final Logger logger = Logger.getLogger(AbstractDAO.class);
	
	private static final class MyBatisConfigurator {
		private static final Logger mybatisConfiguratorLogger = Logger.getLogger(MyBatisConfigurator.class);
		private static SqlSessionFactory sqlSessionFactory = null;
		
		static {
			// calling this method to enable logging to our log4j configuration
			// since the default configuration will point to jakarta commons logging
			LogFactory.useLog4JLogging();
			SqlSessionFactoryBuilder sqlSessionBuilder = null;
			Reader reader = null;
			
			try {
				// reading MyBatis configuration xml
				reader = Resources.getResourceAsReader(Util.getProperty(Constants.Property.MYBATIS_CONFIGURATION));
				
				if(reader != null) {
					sqlSessionBuilder = new SqlSessionFactoryBuilder();
					sqlSessionFactory = sqlSessionBuilder.build(reader, Util.getProperty(Constants.Property.MYBATIS_ENVIRONMENT));
				}
			}
			catch (Exception _exception) {
				Util.handleException(mybatisConfiguratorLogger, "Error configuring MyBatis", _exception);
			}
			
			// clean-up objects
			finally {
				if(reader != null) {
					try {
						reader.close();
					}
					catch(Exception _exception) {
						Util.handleException(mybatisConfiguratorLogger, "Error closing reader object", _exception);
					}
					finally {
						reader = null;
					}
				}
				
				if(sqlSessionBuilder != null) {
					sqlSessionBuilder = null;
				}
			}
		}
		
		/**
		 * Opens a SqlSession and throws a StoredProcedureException if it fails.
		 * 
		 * @return SqlSession
		 * @throws StoredProcedureException
		 */
		protected static SqlSession openSqlSession() throws SQLSessionException {
			SqlSession sqlSession = null;
			
			if(sqlSessionFactory != null) {
				try {
					sqlSession = sqlSessionFactory.openSession();
				}
				catch(Exception _exception) {
					if(_exception instanceof PersistenceException) {
						mybatisConfiguratorLogger.error("openSqlSession() - Error opening sqlSession, details: " +_exception.getMessage());
					}
					else {
						Util.handleException(mybatisConfiguratorLogger, "openSqlSession() - Error opening sqlSession", _exception);
					}
					
					// send notification
					Util.sendEmailNotification(AbstractDAO.class, "openSqlSession", null, null, _exception);
					
					// throw singleton exception
					throw Constants.Exceptions.SQL_SESSION_EXCEPTION;
				}
			}
			else {
				mybatisConfiguratorLogger.error("openSqlSession() - Unable to open sqlSession because sqlSessionFactory is NULL");
				throw Constants.Exceptions.SQL_SESSION_EXCEPTION;
			}
			
			return sqlSession;
		}
	}
	
	protected AbstractDAO() {
	}
	
	/**
	 * Opens a SqlSession and throws a SQLSessionException if it fails.
	 * 
	 * @return SqlSession
	 * @throws StoredProcedureException
	 */
	protected static SqlSession openSqlSession() throws SQLSessionException {
		return MyBatisConfigurator.openSqlSession();
	}
	
	/**
	 * Closes a SqlSession and logs any error as warning, no exception
	 * is thrown if one is unable to close the session because the
	 * stored procedure was still executed successfully.
	 * 
	 * @param SqlSession _sqlSession
	 */
	protected static void closeSqlSession(SqlSession _sqlSession) {
		if(_sqlSession != null) {
			try {
				_sqlSession.close();
				_sqlSession = null;
			}
			catch(Exception _exception) {
				logger.warn("closeSqlSession() - Error closing sqlSession", _exception);
			}
		}
		else {
			logger.warn("closeSqlSession() - Called with a NULL session, doing nothing");
		}
	}
	
	public abstract AbstractDAODTO create(AbstractDAODTO _dto) throws DAOException;
	public abstract AbstractDAODTO read(AbstractDAODTO _dto) throws DAOException;
	public abstract AbstractDAODTO update(AbstractDAODTO _dto) throws DAOException;
	public abstract AbstractDAODTO delete(AbstractDAODTO _dto) throws DAOException;
	public abstract AbstractDAODTO search(AbstractDAODTO _dto) throws DAOException;
}